import 'reflect-metadata';
import { defaultMetadataStorage } from 'class-transformer/cjs/storage';
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
import compression from 'compression';
import express from 'express';
import helmet from 'helmet';
import hpp from 'hpp';
import morgan from 'morgan';
import { useExpressServer, getMetadataArgsStorage } from 'routing-controllers';
import { routingControllersToSpec } from 'routing-controllers-openapi';
import swaggerUi from 'swagger-ui-express';
import { NODE_ENV, PORT, LOG_FORMAT, ORIGIN, CREDENTIALS, API_PREFIX, HOST } from '@config';
import { ErrorMiddleware } from '@middlewares/error.middleware';
import { logger, stream } from '@utils/logger';
import '@/database';
import { AuthMiddleware } from '@middlewares/auth.middleware';
import { RBACMiddleware } from '@middlewares/rbac.middleware';
import { NotFoundMiddleware } from '@middlewares/not-found.middleware';
import { responseTimeMiddleware } from '@middlewares/duration.middleware';
import { AuditLogAfterMiddleware, AuditLogBeforeMiddleware } from '@middlewares/audit-log.middleware';
import { scanRoutes } from '@utils/route-scanner';
import { resolve } from 'path';
import { networkInterfaces } from 'os';
import { writeFileSync, existsSync, mkdirSync } from 'fs';

// 定义控制器类型
type ControllerConstructor = new (...args: any[]) => any;

export class App {
  public app: express.Application;
  public env: string;
  public port: number;
  public host: string;

  constructor(Controllers: ControllerConstructor[]) {
    this.app = express();
    this.env = NODE_ENV || 'development';
    this.port = Number(PORT) || 3000;
    this.host = HOST || 'localhost';

    this.initializeMiddlewares();
    this.initializeRoutes(Controllers);
    this.initializeSwagger(Controllers);
    this.initializeErrorHandling();
    this.app.use(NotFoundMiddleware);

    const apiList = scanRoutes();
    const outFile = resolve(process.cwd(), 'apis/api-routes.json');
    const dir = outFile.substring(0, outFile.lastIndexOf('\\'));
    if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
    writeFileSync(outFile, JSON.stringify(apiList, null, 2));
    logger.info(`[RouteScanner] 已导出 ${apiList.length} 条接口 → ${outFile}`);
  }

  public listen() {
    this.app.listen(this.port, this.host, () => {
      const hosts = this.getListenIPs();
      logger.info(`=================================`);
      logger.info(`======= ENV: ${this.env} =======`);
      logger.info(`🚀 App listening on the port ${this.port}`);
      logger.info(`Press CTRL-C to stop server`);
      logger.info(`> Local http://localhost:${this.port}/`);
      hosts.forEach(host => {
        logger.info(`> Network http://${host}:${this.port}/`);
      });
      logger.info(`Click to open api doc => http://${hosts[0]}:${this.port}/api-docs/`);
      logger.info(`=================================`);
    });
  }

  public getServer() {
    return this.app;
  }

  private initializeMiddlewares() {
    this.app.use(responseTimeMiddleware);
    this.app.use(morgan(LOG_FORMAT, { stream }));
    this.app.use(hpp());
    this.app.use(helmet());
    this.app.use(compression());
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: true }));
    this.app.use(AuthMiddleware);
    this.app.use(RBACMiddleware);
  }

  private initializeRoutes(controllers: ControllerConstructor[]) {
    useExpressServer(this.app, {
      validation: true,
      routePrefix: API_PREFIX,
      cors: {
        origin: ORIGIN,
        credentials: CREDENTIALS,
      },
      controllers,
      middlewares: [AuditLogAfterMiddleware, AuditLogBeforeMiddleware],
      defaultErrorHandler: false,
    });
  }

  private initializeSwagger(controllers: ControllerConstructor[]) {
    const schemas = validationMetadatasToSchemas({
      classTransformerMetadataStorage: defaultMetadataStorage,
      refPointerPrefix: '#/components/schemas/',
    });

    const routingControllersOptions = {
      controllers: controllers,
      routePrefix: API_PREFIX,
    };

    const storage = getMetadataArgsStorage();
    const spec = routingControllersToSpec(storage, routingControllersOptions, {
      components: {
        schemas,
        securitySchemes: {
          bearerAuth: {
            type: 'http',
            scheme: 'bearer',
            bearerFormat: 'JWT',
          },
        },
      },
      info: {
        description: '`TypeScript-Express-Template OpenApi`',
        title: 'TypeScript-Express-Template',
        version: '1.0.0',
      },
      security: [{ bearerAuth: [] }],
    });

    this.app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(spec));
  }

  private initializeErrorHandling() {
    this.app.use(ErrorMiddleware);
  }

  private getListenIPs(): string[] {
    if (this.host !== '0.0.0.0') return [this.host];

    const nets = networkInterfaces();
    const ips: string[] = [];
    for (const name of Object.keys(nets)) {
      const iface = nets[name];
      if (!iface) continue;
      for (const info of iface) {
        if (info.family === 'IPv4' && !info.internal) {
          ips.push(info.address);
        }
      }
    }
    return ips.length ? ips : ['127.0.0.1']; // 保底
  }
}
